#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
统一混淆系统 - 交互式启动脚本
支持渐进式选择或直接命令行参数
"""

import os
import sys
import argparse
from typing import Dict, Any, List, Optional

# 添加项目根目录到Python路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))


class InteractiveMenu:
    """交互式菜单系统"""

    def __init__(self):
        self.config: Dict[str, Any] = {}

    def _detect_project_type(self, project_path: str) -> str:
        """
        检测项目类型
        复用 ProjectCloner 的项目分析逻辑
        """
        # 检查是否是 Flutter 项目
        if os.path.exists(os.path.join(project_path, 'pubspec.yaml')):
            return 'flutter'

        # 简化的项目类型检测（基于 ProjectCloner._analyze_project）
        has_ios_indicators = False
        has_android_indicators = False

        for item in os.listdir(project_path):
            # iOS 项目标识
            if item.endswith(('.xcodeproj', '.xcworkspace')) or item == 'Podfile':
                has_ios_indicators = True
            # Android 项目标识
            if item in ['build.gradle', 'build.gradle.kts', 'settings.gradle', 'settings.gradle.kts']:
                has_android_indicators = True

        # 检查子目录
        if not has_android_indicators:
            app_dir = os.path.join(project_path, 'app')
            if os.path.exists(app_dir):
                for item in os.listdir(app_dir):
                    if item in ['build.gradle', 'build.gradle.kts']:
                        has_android_indicators = True
                        break

        if has_ios_indicators and has_android_indicators:
            return 'flutter'  # 或者是混合项目
        elif has_ios_indicators:
            return 'ios'
        elif has_android_indicators:
            return 'android'
        else:
            return 'unknown'

    def _validate_config_file(self, config_path: str) -> bool:
        """验证配置文件格式"""
        try:
            import json
            with open(config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)

            # 检查必需的字段
            if not isinstance(config, dict):
                print("❌ 配置文件必须是 JSON 对象")
                return False

            # 显示配置文件摘要
            print("\n📋 配置文件内容预览:")
            for key in list(config.keys())[:5]:  # 显示前5个键
                value_type = type(config[key]).__name__
                print(f"  • {key}: {value_type}")

            if len(config.keys()) > 5:
                print(f"  ... 还有 {len(config.keys()) - 5} 个配置项")

            return True

        except json.JSONDecodeError as e:
            print(f"❌ JSON 格式错误: {e}")
            return False
        except Exception as e:
            print(f"❌ 读取配置文件失败: {e}")
            return False

    def _validate_bundle_id(self, bundle_id: str) -> bool:
        """验证 iOS Bundle ID 格式"""
        if not bundle_id:
            return False

        # Bundle ID 规则:
        # - 只能包含字母、数字、连字符(-)和点(.)
        # - 必须包含至少一个点(.)
        # - 不能以点或连字符开头/结尾
        # - 每个部分不能为空
        # - 通常格式: com.company.appname

        import re
        # 检查是否只包含合法字符
        if not re.match(r'^[a-zA-Z0-9.-]+$', bundle_id):
            print(f"❌ Bundle ID 只能包含字母、数字、连字符和点")
            return False

        # 必须包含至少一个点
        if '.' not in bundle_id:
            print(f"❌ Bundle ID 必须包含至少一个点，格式如: com.company.app")
            return False

        # 不能以点或连字符开头/结尾
        if bundle_id.startswith(('.', '-')) or bundle_id.endswith(('.', '-')):
            print(f"❌ Bundle ID 不能以点或连字符开头/结尾")
            return False

        # 检查每个部分是否为空或无效
        parts = bundle_id.split('.')
        for part in parts:
            if not part:
                print(f"❌ Bundle ID 各部分不能为空")
                return False
            if part.startswith('-') or part.endswith('-'):
                print(f"❌ Bundle ID 各部分不能以连字符开头/结尾")
                return False

        return True

    def _validate_package_name(self, package_name: str) -> bool:
        """验证 Android 包名格式"""
        if not package_name:
            return False

        # Android 包名规则:
        # - 只能包含小写字母、数字和点(.)
        # - 必须包含至少一个点(.)
        # - 不能以数字或点开头
        # - 每个部分必须以字母开头
        # - 通常格式: com.company.appname

        import re
        # 检查是否只包含合法字符（小写字母、数字、点）
        if not re.match(r'^[a-z0-9.]+$', package_name):
            print(f"❌ Android 包名只能包含小写字母、数字和点")
            return False

        # 必须包含至少一个点
        if '.' not in package_name:
            print(f"❌ Android 包名必须包含至少一个点，格式如: com.company.app")
            return False

        # 不能以点开头/结尾
        if package_name.startswith('.') or package_name.endswith('.'):
            print(f"❌ Android 包名不能以点开头/结尾")
            return False

        # 检查每个部分
        parts = package_name.split('.')
        for part in parts:
            if not part:
                print(f"❌ Android 包名各部分不能为空")
                return False
            # 每个部分必须以字母开头
            if not part[0].isalpha():
                print(f"❌ Android 包名各部分必须以字母开头: '{part}'")
                return False
            # 不能是Java关键字
            java_keywords = {'abstract', 'continue', 'for', 'new', 'switch', 'assert',
                           'default', 'goto', 'package', 'synchronized', 'boolean', 'do',
                           'if', 'private', 'this', 'break', 'double', 'implements',
                           'protected', 'throw', 'byte', 'else', 'import', 'public',
                           'throws', 'case', 'enum', 'instanceof', 'return', 'transient',
                           'catch', 'extends', 'int', 'short', 'try', 'char', 'final',
                           'interface', 'static', 'void', 'class', 'finally', 'long',
                           'strictfp', 'volatile', 'const', 'float', 'native', 'super', 'while'}
            if part in java_keywords:
                print(f"❌ Android 包名不能使用 Java 关键字: '{part}'")
                return False

        return True

    def get_bundle_id(self, prompt: str, required: bool = False, allow_empty: bool = True) -> Optional[str]:
        """获取并验证 Bundle ID"""
        while True:
            bundle_id = input(f"{prompt} (留空将使用自动生成): ").strip()

            if not bundle_id:
                if allow_empty:
                    print("ℹ️  将使用自动生成的 Bundle ID")
                    return None
                if not required:
                    return None
                print("❌ Bundle ID 不能为空")
                continue

            if self._validate_bundle_id(bundle_id):
                return bundle_id

            print("💡 正确格式示例: com.company.appname")
            retry = self.get_yes_no("是否重新输入", True)
            if not retry:
                if required:
                    sys.exit(0)
                else:
                    return None

    def get_package_name(self, prompt: str, required: bool = False, allow_empty: bool = True) -> Optional[str]:
        """获取并验证 Android 包名"""
        while True:
            package_name = input(f"{prompt} (留空将使用自动生成): ").strip()

            if not package_name:
                if allow_empty:
                    print("ℹ️  将使用自动生成的包名")
                    return None
                if not required:
                    return None
                print("❌ 包名不能为空")
                continue

            if self._validate_package_name(package_name):
                return package_name

            print("💡 正确格式示例: com.company.appname (仅小写)")
            retry = self.get_yes_no("是否重新输入", True)
            if not retry:
                if required:
                    sys.exit(0)
                else:
                    return None

    def clear_screen(self):
        """清屏"""
        os.system('cls' if os.name == 'nt' else 'clear')

    def print_header(self, title: str):
        """打印标题"""
        print("\n" + "=" * 60)
        print(f"  {title}")
        print("=" * 60 + "\n")

    def get_choice(self, prompt: str, options: List[str], default: Optional[str] = None) -> str:
        """获取用户选择"""
        for i, opt in enumerate(options, 1):
            print(f"  {i}. {opt}")

        if default:
            prompt = f"{prompt} [默认: {default}]"

        while True:
            choice = input(f"\n{prompt}: ").strip()
            if not choice and default:
                return default
            if choice.isdigit() and 1 <= int(choice) <= len(options):
                return options[int(choice) - 1]
            if choice in options:
                return choice
            print("❌ 无效选择，请重试")

    def get_input(self, prompt: str, default: Optional[str] = None, required: bool = True) -> str:
        """获取用户输入"""
        if default:
            prompt = f"{prompt} [默认: {default}]"

        while True:
            value = input(f"{prompt}: ").strip()
            if not value and default:
                return default
            if value or not required:
                return value
            print("❌ 此项为必填项，请输入")

    def get_path(self, prompt: str, must_exist: bool = True, is_directory: bool = False) -> str:
        """获取路径输入并验证"""
        while True:
            path = input(f"{prompt}: ").strip()

            if not path:
                print("❌ 路径不能为空，请输入有效路径")
                continue

            # 展开用户目录符号 ~
            path = os.path.expanduser(path)

            # 转换为绝对路径
            if not os.path.isabs(path):
                path = os.path.abspath(path)

            if must_exist:
                if not os.path.exists(path):
                    print(f"❌ 路径不存在: {path}")
                    retry = self.get_yes_no("是否重新输入", True)
                    if retry:
                        continue
                    else:
                        sys.exit(0)

                if is_directory and not os.path.isdir(path):
                    print(f"❌ 路径不是目录: {path}")
                    continue

                if not is_directory and os.path.isdir(path):
                    print(f"⚠️  警告: 这是一个目录，不是文件")
                    if not self.get_yes_no("是否继续", False):
                        continue
            else:
                # 如果路径不需要存在，检查父目录是否存在
                parent_dir = os.path.dirname(path)
                if parent_dir and not os.path.exists(parent_dir):
                    print(f"❌ 父目录不存在: {parent_dir}")
                    if self.get_yes_no("是否创建父目录", True):
                        try:
                            os.makedirs(parent_dir, exist_ok=True)
                            print(f"✅ 已创建目录: {parent_dir}")
                        except Exception as e:
                            print(f"❌ 创建目录失败: {e}")
                            continue
                    else:
                        continue

            return path

    def get_yes_no(self, prompt: str, default: bool = True) -> bool:
        """获取是/否选择"""
        default_str = "Y/n" if default else "y/N"
        choice = input(f"{prompt} [{default_str}]: ").strip().lower()

        if not choice:
            return default
        return choice in ['y', 'yes', '是']

    def select_action(self) -> str:
        """选择执行动作"""
        self.print_header("选择操作类型")

        actions = [
            "混淆项目 (obfuscate)",
            "恢复项目 (restore)",
            "提取符号 (extract)",
            "注入代码 (inject)",
            "克隆项目 (clone)",
            "备份管理 (backup)",
            "性能测试 (benchmark)",
            "退出 (exit)"
        ]

        action_map = {
            "混淆项目 (obfuscate)": "obfuscate",
            "恢复项目 (restore)": "restore",
            "提取符号 (extract)": "extract",
            "注入代码 (inject)": "inject",
            "克隆项目 (clone)": "clone",
            "备份管理 (backup)": "backup",
            "性能测试 (benchmark)": "benchmark",
            "退出 (exit)": "exit"
        }

        choice = self.get_choice("请选择操作", actions)
        return action_map[choice]

    def select_platform(self) -> str:
        """选择平台"""
        platforms = ["iOS", "Android", "统一平台 (推荐)"]
        platform_map = {
            "iOS": "ios",
            "Android": "android",
            "统一平台 (推荐)": "unified"
        }
        choice = self.get_choice("选择目标平台", platforms, "统一平台 (推荐)")
        return platform_map[choice]

    def select_mode(self) -> str:
        """选择混淆模式"""
        modes = ["Swift", "Objective-C", "混合模式"]
        mode_map = {
            "Swift": "swift",
            "Objective-C": "objc",
            "混合模式": "mixed"
        }
        choice = self.get_choice("选择混淆模式", modes, "混合模式")
        return mode_map[choice]

    def select_obfuscation_level(self) -> str:
        """选择混淆级别"""
        levels = [
            "基础 (basic) - 快速，适合开发阶段",
            "标准 (standard) - 平衡性能与效果",
            "高级 (advanced) - 深度混淆",
            "最大 (maximum) - 最强混淆，较慢"
        ]
        level_map = {
            "基础 (basic) - 快速，适合开发阶段": "basic",
            "标准 (standard) - 平衡性能与效果": "standard",
            "高级 (advanced) - 深度混淆": "advanced",
            "最大 (maximum) - 最强混淆，较慢": "maximum"
        }
        choice = self.get_choice("选择混淆级别", levels, "标准 (standard) - 平衡性能与效果")
        return level_map[choice]

    def configure_obfuscation(self):
        """配置混淆参数"""
        self.print_header("配置混淆参数")

        # 项目路径
        self.config['project_path'] = self.get_input("项目路径", required=True)
        if not os.path.exists(self.config['project_path']):
            print(f"⚠️  警告: 路径不存在 {self.config['project_path']}")
            if not self.get_yes_no("是否继续？", False):
                sys.exit(0)

        # 平台和模式
        self.config['platform'] = self.select_platform()
        self.config['mode'] = self.select_mode()

        # 混淆级别
        self.config['obfuscation_level'] = self.select_obfuscation_level()

        # 高级选项
        print("\n--- 高级选项 ---")
        self.config['incremental'] = self.get_yes_no("启用增量分析（仅处理变更文件）", True)
        self.config['parallel'] = self.get_yes_no("启用并行处理", True)

        if self.config['parallel']:
            workers = self.get_input("最大工作线程数", "4", False)
            self.config['max_workers'] = int(workers) if workers.isdigit() else 4

        self.config['use_cache'] = self.get_yes_no("启用缓存", True)
        self.config['backup'] = self.get_yes_no("执行前备份", True)
        self.config['dry_run'] = self.get_yes_no("模拟运行（不实际修改文件）", False)
        self.config['verbose'] = self.get_yes_no("详细日志输出", False)

        # 配置文件
        use_config = self.get_yes_no("使用配置文件", False)
        if use_config:
            self.config['config_file'] = self.get_input("配置文件路径", required=False)

    def configure_clone(self):
        """配置克隆参数"""
        self.print_header("配置克隆参数")

        # 第一步：获取并验证源项目路径
        self.config['project_path'] = self.get_path("源项目路径", must_exist=True, is_directory=True)

        # 第二步：智能检测项目类型
        print("\n🔍 正在检测项目类型...")
        project_type = self._detect_project_type(self.config['project_path'])

        if project_type == 'ios':
            print("✅ 检测到 iOS 项目")
        elif project_type == 'android':
            print("✅ 检测到 Android 项目")
        elif project_type == 'flutter':
            print("✅ 检测到 Flutter 项目（包含 iOS 和 Android）")
        else:
            print("⚠️  无法确定项目类型，将作为通用项目处理")

        # 第三步：获取目标路径
        self.config['target_path'] = self.get_path("目标路径", must_exist=False, is_directory=True)

        # 检查目标路径是否已存在
        if os.path.exists(self.config['target_path']):
            print(f"⚠️  目标路径已存在: {self.config['target_path']}")
            if self.get_yes_no("是否删除并重新创建", False):
                import shutil
                try:
                    print(f"🗑️  正在删除旧目录...")
                    shutil.rmtree(self.config['target_path'])
                    print(f"✅ 已删除: {self.config['target_path']}")
                except Exception as e:
                    print(f"❌ 删除失败: {e}")
                    sys.exit(1)
            else:
                print("❌ 操作已取消")
                sys.exit(0)

        # 第四步：根据项目类型配置标识符
        print("\n--- 标识符配置 ---")
        if project_type in ['ios', 'flutter']:
            print("💡 Bundle ID 格式示例: com.company.appname")
            self.config['bundle_id'] = self.get_bundle_id("新的Bundle ID (iOS)", required=False)
        else:
            self.config['bundle_id'] = None

        if project_type in ['android', 'flutter']:
            print("💡 包名格式示例: com.company.appname (仅小写字母)")
            self.config['package_name'] = self.get_package_name("新的包名 (Android)", required=False)
        else:
            self.config['package_name'] = None

        self.config['project_name'] = self.get_input("新的项目名称", required=False)

        # 第五步：询问是否使用配置文件
        print("\n--- 高级选项 ---")
        use_config = self.get_yes_no("是否使用配置文件进行混淆", False)
        if use_config:
            config_path = self.get_path("配置文件路径", must_exist=True, is_directory=False)
            if self._validate_config_file(config_path):
                self.config['config_file'] = config_path
                print(f"✅ 配置文件验证通过: {config_path}")
            else:
                print("❌ 配置文件格式不正确")
                if not self.get_yes_no("是否继续（使用默认配置）", True):
                    sys.exit(0)

        self.config['modify_fingerprints'] = self.get_yes_no("修改文件指纹", True)

    def configure_backup(self):
        """配置备份参数"""
        self.print_header("备份管理")

        backup_actions = [
            "创建完整备份",
            "创建增量备份",
            "列出所有备份",
            "清理旧备份"
        ]

        action = self.get_choice("选择备份操作", backup_actions)

        self.config['project_path'] = self.get_input("项目路径", required=True)

        if "增量" in action:
            self.config['action'] = 'incremental-backup'
            self.config['base_backup'] = self.get_input("基础备份路径（留空自动查找）", required=False)
        elif "列出" in action:
            self.config['action'] = 'list-backups'
        elif "清理" in action:
            self.config['action'] = 'clean-old-backups'
            keep = self.get_input("保留最近几个备份", "5", False)
            self.config['keep_backups'] = int(keep) if keep.isdigit() else 5
        else:
            self.config['action'] = 'backup'

    def show_summary(self):
        """显示配置摘要"""
        self.print_header("配置摘要")

        for key, value in self.config.items():
            if value:
                print(f"  {key}: {value}")

        print("\n")
        return self.get_yes_no("确认执行", True)

    def run_interactive(self):
        """运行交互式配置"""
        self.clear_screen()
        print("\n🚀 统一混淆系统 - 交互式配置\n")

        # 选择动作
        action = self.select_action()

        if action == "exit":
            print("👋 再见！")
            sys.exit(0)

        if action == "benchmark":
            self.run_benchmark()
            return

        self.config['action'] = action

        # 根据动作配置参数
        if action == "obfuscate":
            self.configure_obfuscation()
        elif action == "clone":
            self.configure_clone()
        elif action == "backup":
            self.configure_backup()
        elif action in ["restore", "extract", "inject"]:
            self.config['project_path'] = self.get_input("项目路径", required=True)
            self.config['mode'] = self.select_mode()

        # 显示摘要并确认
        if not self.show_summary():
            print("❌ 操作已取消")
            sys.exit(0)

        # 执行
        self.execute()

    def execute(self):
        """执行混淆"""
        print("\n🔄 开始执行...\n")

        # 构建命令行参数
        args = self.build_args()

        # 导入并运行main.py的主函数
        from main import main as main_obfuscate

        # 替换sys.argv
        sys.argv = ['main.py'] + args

        try:
            main_obfuscate()
            print("\n✅ 执行完成！")
        except Exception as e:
            print(f"\n❌ 执行失败: {e}")
            sys.exit(1)

    def build_args(self) -> List[str]:
        """构建命令行参数"""
        args = []

        # 基础参数
        if 'project_path' in self.config:
            args.extend(['--project-path', self.config['project_path']])

        if 'action' in self.config:
            args.extend(['--action', self.config['action']])

        if 'mode' in self.config:
            args.extend(['--mode', self.config['mode']])

        if 'obfuscation_level' in self.config:
            args.extend(['--obfuscation-level', self.config['obfuscation_level']])

        # 克隆相关
        if 'target_path' in self.config:
            args.extend(['--target-path', self.config['target_path']])

        if 'bundle_id' in self.config and self.config['bundle_id']:
            args.extend(['--bundle-id', self.config['bundle_id']])

        if 'package_name' in self.config and self.config['package_name']:
            args.extend(['--package-name', self.config['package_name']])

        if 'project_name' in self.config and self.config['project_name']:
            args.extend(['--project-name', self.config['project_name']])

        # 备份相关
        if 'base_backup' in self.config and self.config['base_backup']:
            args.extend(['--base-backup', self.config['base_backup']])

        if 'keep_backups' in self.config:
            args.extend(['--keep-backups', str(self.config['keep_backups'])])

        # 布尔选项
        if self.config.get('dry_run'):
            args.append('--dry-run')

        if self.config.get('verbose'):
            args.append('--verbose')

        if 'config_file' in self.config and self.config['config_file']:
            args.extend(['--config', self.config['config_file']])

        return args

    def run_benchmark(self):
        """运行性能测试"""
        self.print_header("性能测试")

        test_project = self.get_input("测试项目路径", "./test_project")

        if not os.path.exists(test_project):
            print(f"❌ 测试项目不存在: {test_project}")
            return

        print("\n🔄 运行性能测试...\n")

        from core.obfuscation_module.obfuscator import Obfuscator

        # 临时修改sys.argv
        original_argv = sys.argv
        sys.argv = ['main_obfuscator.py', '--benchmark']

        try:
            Obfuscator.compare_performance()
        finally:
            sys.argv = original_argv


def parse_args():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(
        description='统一混淆系统 - 启动脚本',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  # 交互式模式
  python start_obfuscator.py

  # 快速混淆（使用默认配置）
  python start_obfuscator.py --quick /path/to/project

  # 使用配置文件
  python start_obfuscator.py --config config.json

  # 克隆项目
  python start_obfuscator.py --clone /source/path --target /target/path

  # 性能测试
  python start_obfuscator.py --benchmark
        """
    )

    parser.add_argument('--interactive', '-i', action='store_true',
                       help='交互式模式（默认）')
    parser.add_argument('--quick', '-q', metavar='PROJECT_PATH',
                       help='快速混淆模式，使用默认配置')
    parser.add_argument('--config', '-c', metavar='CONFIG_FILE',
                       help='使用配置文件')
    parser.add_argument('--clone', metavar='SOURCE_PATH',
                       help='克隆项目源路径')
    parser.add_argument('--target', '-t', metavar='TARGET_PATH',
                       help='克隆目标路径')
    parser.add_argument('--benchmark', '-b', action='store_true',
                       help='运行性能测试')

    return parser.parse_args()


def quick_obfuscate(project_path: str):
    """快速混淆（使用默认配置）"""
    print(f"🚀 快速混淆模式: {project_path}\n")

    if not os.path.exists(project_path):
        print(f"❌ 项目路径不存在: {project_path}")
        sys.exit(1)

    # 构建默认参数
    args = [
        'main.py',
        '--project-path', project_path,
        '--action', 'obfuscate',
        '--mode', 'mixed',
        '--obfuscation-level', 'standard'
    ]

    sys.argv = args

    from main import main as main_obfuscate

    try:
        main_obfuscate()
        print("\n✅ 快速混淆完成！")
    except Exception as e:
        print(f"\n❌ 混淆失败: {e}")
        sys.exit(1)


def main():
    """主入口"""
    args = parse_args()

    # 性能测试模式
    if args.benchmark:
        from core.obfuscation_module.obfuscator import Obfuscator
        sys.argv = ['main_obfuscator.py', '--benchmark']
        Obfuscator.compare_performance()
        return

    # 快速混淆模式
    if args.quick:
        quick_obfuscate(args.quick)
        return

    # 克隆模式
    if args.clone:
        if not args.target:
            print("❌ 克隆模式需要指定目标路径 --target")
            sys.exit(1)

        sys.argv = [
            'main.py',
            '--project-path', args.clone,
            '--action', 'clone',
            '--target-path', args.target
        ]

        from main import main as main_obfuscate
        main_obfuscate()
        return

    # 使用配置文件
    if args.config:
        sys.argv = [
            'main.py',
            '--config', args.config,
            '--action', 'obfuscate'
        ]

        from main import main as main_obfuscate
        main_obfuscate()
        return

    # 默认交互式模式
    menu = InteractiveMenu()
    menu.run_interactive()


if __name__ == '__main__':
    main()